/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-核心依赖
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.support;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.taipingframework.boot.web.response.result.ApiResult;
import com.taipingframework.boot.web.response.status.ApiStatusEnum;
import com.taipingframework.utility.constant.ObjectConstant;
import com.taipingframework.utility.exception.UtilityException;
import com.taipingframework.utility.extend.StringHelper;
import com.taipingframework.utility.web.IApiStatus;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Properties;
import java.util.StringJoiner;

public interface ApiHelper {

    /**
     * 唯一标识该接口实现类每个实例的字段名称。
     * 按照约定，已知的是继承该接口的类型是枚举类，每个枚举实例都要实现 方法 getCode()。
     * 按照规范，在 Java Bean 中，方法`getCode()`对应的字段名应该是`code`。
     * 而方法 toJSON() 正是 按照规范 序列化每个枚举实例对象的。
     * 所以，为了能够使hutool成功 反序列化 该接口实现类的每个实例对象，此处显式声明该字段名的常量是必要的。
     * #
     * # hutool核心代码参考：
     * # final ConverterRegistry registry = ConverterRegistry.getInstance();
     * # Object d0601 = registry.convert(clazz, "D0601");
     * #
     * 而在Jackson中，使用注解 @JsonValue 等价替换了此种实现方式。
     */
    String OBJECT_IDENTITY_KEY = "code";

    /**
     * 对象的包名
     * 当一个接口有多个 Java Bean 实现类时，对子类的实例对象进行序列化后得到 json字符串。
     * 那么，在反序列化时，将要面临多态的问题，即，我应该反序列化为哪个具体的实现类型？
     * 在Jackson中，通过注解 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 可以轻易解决该问题。
     * 在编码分层结构调整之后，核心工具包不应该依赖Jackson，毕竟它的体积太大了。
     * 这就好像是，你在写一个工具类时，必须要依赖了一个SpringBoot工程的启动jar包一样，很不合理。
     * 所以，此处不得不面对并解决`多态场景下json字符串反序列化`的问题。思路如下：
     * （1）在将实现类实例对象序列化为json字符串时，使用特殊字段`@classname`标识该json字符串在序列化之前的实现类型；
     * （2）在反序列化时，拿到该标识字段`@classname`所存储的值，将json字符串反序列化为字段`@classname`所表示的引用类型。
     * 不得不说，Jackson是强大的，也是伟大的，虽然配置繁琐，但在使用过程中极具灵活性，能够满足各种复杂业务场景的序列化需求，必须是首选！
     */
    String PACKAGE_PATH_KEY = "@classname";

    /**
     * 根据`异常消息`创建 接口异常返回结果
     */
    static ApiResult<Serializable> buildResult(String errMsg, @NonNull StringJoiner joiner) {
        return buildResult(null, errMsg, joiner);
    }

    /**
     * 根据`异常消息`创建 接口异常返回结果
     */
    static ApiResult<Serializable> buildResult(String requestId, String errMsg, @NonNull StringJoiner joiner) {
        ApiResult<Serializable> result = StringUtils.isNotEmpty(requestId)
                ? ApiResult.build(requestId)
                : ApiResult.build();
        try {
            IApiStatus iApiStatus = ApiHelper.fromJSON(errMsg);
            result.setStatus(iApiStatus);
            joiner.add(iApiStatus.getMessage());
        } catch (Exception ignored) { // 若捕获到未声明的异常状态，则统一按照`系统执行出错`处理！
            result.setStatus(ApiStatusEnum.ERR_B0001);
            joiner.add(errMsg);
        }

        return result;
    }

    /**
     * 将继承该接口的枚举对象转换为JSON字符串
     */
    static String toJSON(IApiStatus status) throws UtilityException {
        try {
            Properties properties = new Properties();

            Class<? extends IApiStatus> clazz = status.getClass();
            properties.put(PACKAGE_PATH_KEY, clazz.getName());
            for (Method method : IApiStatus.class.getDeclaredMethods()) {
                if (!StringHelper.startWith(method.getName(), ObjectConstant.PREFIX_METHOD_GETTER)) {
                    continue;
                }
                String fieldName = StringHelper.trimStart(method.getName(), ObjectConstant.PREFIX_METHOD_GETTER);
                String word1st = String.valueOf(fieldName.charAt(0));
                StringJoiner joiner = new StringJoiner("");
                joiner.add(word1st.toLowerCase());
                joiner.add(StringHelper.trimStart(fieldName, word1st));
                properties.put(joiner.toString(), clazz.getMethod(method.getName()).invoke(status));
            }
            return JSONUtil.toJsonStr(properties);
        } catch (Exception ex) {
            throw new UtilityException(ex);
        }
    }

    /**
     * 将JSON字符串转换为继承该接口的枚举对象
     */
    static IApiStatus fromJSON(String json) throws UtilityException {
        try {
            if (StringUtils.isEmpty(json)) {
                throw new Exception();
            }
            if (!JSONUtil.isTypeJSONObject(json)) {
                throw new Exception();
            }
            JSONObject jsonObject = JSONUtil.parseObj(json);
            String classname = jsonObject.getStr(PACKAGE_PATH_KEY, null);
            if (StringUtils.isBlank(classname)) {
                throw new Exception();
            }
            Class<?> clazz = Class.forName(classname);
            if (!IApiStatus.class.isAssignableFrom(clazz)) {
                throw new Exception();
            }
            return (IApiStatus) JSONUtil.toBean(json, clazz);
        } catch (Exception ex) {
            throw new UtilityException(ex);
        }
    }

}
